#pragma once
#include <iostream>
#include <unordered_map>
#include <memory>
#include <functional>
#include "Socket.hpp"
#include "epoller.hpp"
#include "NoCopy.hpp"
#include "Comm.hpp"

// ET模式，边缘触发，非阻塞fd，读或者写循环

class Connection;
class Tcpserver;

// 设则我们的回调函数指针
using func_t = std::function<void(std::weak_ptr<Connection>)>;

// connection是为了处理我们每个fd有自己单独的缓冲区，ET模式下
// 继承NoCopy防止拷贝或者赋值
class Connection
{
public:
    Connection(int sockfd) : sockfd_(sockfd),inbuffer_(""),outbuffer_("")
    {
    }

    // 设置每个fd对应connection的回调方法
    void SetCb(func_t recvcb, func_t sendcb, func_t excecb)
    {
        recvcb_ = recvcb;
        sendcb_ = sendcb;
        excecb_ = excecb;
    }

    std::string &GetInbuffer()
    {
        return inbuffer_;
    }

    int GetSockFd()
    {
        return sockfd_;
    }

    void AppendInbuffer(const std::string &info)
    {
        inbuffer_ += info;
    }

    void AppendOutbuffer(const std::string &info)
    {
        outbuffer_ += info;
    }

    std::string &GetOutbuffer()
    {
        return outbuffer_;
    }

    ~Connection()
    {
    }

private:
    int sockfd_;
    // 每个fd用来存放自己数据的缓冲区，ET模式
    std::string inbuffer_;
    std::string outbuffer_;

public:
    // 我们输入、输出、异常对应的回调函数
    func_t recvcb_;
    func_t sendcb_;
    func_t excecb_;

    // 回指向我们的服务器
    std::weak_ptr<Tcpserver> tsvr_ptr_;

    // 存放客户端的信息
    std::string clientip_;
    uint16_t clientport_;
};

// buffer大小
const static int buffersize = 128;

// epoll的每次检测时间
const static int timeout = 3000;
// 事件的种类
uint32_t EVENT_IN = (EPOLLIN | EPOLLET);
uint32_t EVENT_OUT = (EPOLLOUT | EPOLLET);
// 获取上来的就绪队列数量
const static int num = 10;

// 第二个继承的可以提供当前对象this对应的shared_ptr
class Tcpserver : public NoCopy, public std::enable_shared_from_this<Tcpserver>
{
public:
    Tcpserver(uint16_t port, func_t onhandler) : port_(port), listensock_(new Sock()), epoll_ptr(new Epoller), onhandler_(onhandler)
    {
    }
    ~Tcpserver()
    {
    }

    // 初始化
    void Init()
    {
        // 服务器初始化
        listensock_->Socket();
        // 设置非阻塞满足ET模式
        SetNonBlock(listensock_->GetSockfd());
        listensock_->Bind(port_);
        listensock_->Listen();

        lg(Info, "listen sock create success ,fd is: %d", listensock_->GetSockfd());

        // 为listensock创建connection对象。
        AddConnection(listensock_->GetSockfd(), EVENT_IN, std::bind(&Tcpserver::Accepter, this, std::placeholders::_1), nullptr, nullptr);
    }

    // loop循环，循环调用分配器和打印，与我们之前的一样
    void loop()
    {
        while (true)
        {
            Dispatcher();
            PrintConnections();
        }
    }

private:
    // 添加connection
    void AddConnection(int sockfd, uint32_t event, func_t recvcb, func_t sendcb, func_t excecb, const std::string &ip = "0.0.0.0", const uint16_t &port = 0)
    {
        // 1.为fd创建对应的connection对象
        std::shared_ptr<Connection> newcon_ptr = std::make_shared<Connection>(sockfd);

        // 2.为对象补充我们的成员变量值，回调函数、回调指针、以及ip和port，并且将对象加入到我们的unordered_map
        newcon_ptr->SetCb(recvcb, sendcb, excecb);
        newcon_ptr->tsvr_ptr_ = shared_from_this();
        newcon_ptr->clientip_ = ip;
        newcon_ptr->clientport_ = port;
        connections_[sockfd] = newcon_ptr;

        // 3.将事件加入到我们的内核中
        epoll_ptr->EpollerUpdate(EPOLL_CTL_ADD, sockfd, event);
    }

    // 我们listensoc的读回调函数
    void Accepter(std::weak_ptr<Connection> con_ptr)
    {
        while (true)
        {
            // 获取新的链接
            std::string clientip;
            uint16_t clientport;
            int sockfd = listensock_->Accept(&clientip, &clientport);
            if (sockfd > 0)
            {
                lg(Info, "get a new link !!,fd is: %d", sockfd);

                // 设则文件描述符为非阻塞
                SetNonBlock(sockfd);
                // 当获取到新的链接后，我们就需要写入我们每个fd对应个回调函数了，但是基本都一样，就只有listensock不一样。
                AddConnection(sockfd, EVENT_IN, std::bind(&Tcpserver::Recver, this, std::placeholders::_1),
                              std::bind(&Tcpserver::Sender, this, std::placeholders::_1),
                              std::bind(&Tcpserver::Excepter, this, std::placeholders::_1), clientip, clientport);
            }
            else
            {
                // 链接全部获取完毕，退出
                if (errno == EWOULDBLOCK)
                    break;
                // 中断错误，继续读取即可
                else if (errno == EINTR)
                    continue;
                // 链接错误，返回
                else
                    return;
            }
        }
    }

    void Recver(std::weak_ptr<Connection> con_ptr)
    {
        // 判断weak_ptr是否指向空
        if (con_ptr.expired())
            return;
        // 接收shared_ptr，可以调用所指对象的函数
        auto it = con_ptr.lock();
        int sockfd = it->GetSockFd();
        while (true)
        {
            char buffer[buffersize];
            // 清空缓冲区
            memset(buffer, 0, sizeof buffer);
            // 非阻塞读取
            int n = recv(sockfd, buffer, buffersize - 1, 0); // 0其实是代表阻塞读
            if (n > 0)
            {
                buffer[n] = 0;
                // 将读入的数据加入inbuffer
                it->AppendInbuffer(buffer);
            }
            // 客户端主动断开链接
            else if (n == 0)
            {
                lg(Info, "client quit,fd is: %d, client ip : %s,client port : %d", sockfd, it->clientip_.c_str(), it->clientport_);
                it->excecb_(it);
                return;
            }
            // 发生错误
            else
            {
                // 对方的发送缓冲区读取完了
                if (errno == EWOULDBLOCK)
                    break;
                else if (errno == EINTR)
                    continue; // 发生中断
                else
                {
                    lg(Info, "server recv error！！！,fd is: %d, client ip : %s,client port : %d", sockfd, it->clientip_.c_str(), it->clientport_);
                    it->excecb_(it);
                    return;
                }
            }
        }
        onhandler_(it);
    }

    void Sender(std::weak_ptr<Connection> con_ptr)
    {
        if (con_ptr.expired())
            return;
        auto it = con_ptr.lock();

        std::string &outbuffer = it->GetOutbuffer();
        int sockfd = it->GetSockFd();
        while (true)
        {
            // 发送服务端发送缓冲区的内容
            int n = send(sockfd, outbuffer.c_str(), outbuffer.size(), 0);
            if (n > 0)
            {
                outbuffer.erase(0, n);
                // 如果发送缓冲区空了，就退出
                if (outbuffer.empty())
                    break;
            }
            else if (n == 0)
            {
                return;
            }
            else
            {
                if (errno == EWOULDBLOCK)
                    break;
                else if (errno == EINTR)
                    continue;
                else
                {
                    lg(Info, "server send error！！！,fd is: %d, client ip : %s,client port : %d", sockfd, it->clientip_.c_str(), it->clientport_);
                    it->excecb_(it);
                    return;
                }
            }

            // 如果发送缓冲区还有残留，就关心写事件。
            if (!outbuffer.empty())
            {
                EnableEvent(sockfd, true, true);
            }
            // 如果发送完了，就关闭写事件的关心，因为写事件一般都处于就绪状态，那就会一直处理读，cpu效率会降低。
            else
            {
                EnableEvent(sockfd, true, false);
            }
        }
    }

    void EnableEvent(int sockfd, bool readable, bool writeable)
    {
        uint32_t event = 0;
        event |= (readable ? EPOLLIN : 0) | (writeable ? EPOLLOUT : 0) | EPOLLET;
        epoll_ptr->EpollerUpdate(EPOLL_CTL_MOD, sockfd, event);
    }

    void Excepter(std::weak_ptr<Connection> con_ptr)
    {
        if (con_ptr.expired())
            return;
        auto it = con_ptr.lock();

        int sockfd = it->GetSockFd();
        lg(Warning, "fd is : %d,client info [%s:%d] excepter handle!!", sockfd, it->clientip_, it->clientport_);

        // 1.将错误的文件描述符从内核中删除
        epoll_ptr->EpollerUpdate(EPOLL_CTL_DEL, sockfd, 0);

        // 2.关闭我们的文件描述符
        close(sockfd);
        lg(Info, "fd : %d is close!!!", sockfd);

        // 3.将sockfd从connections中删除
        lg(Debug, "remove %d from _connections...\n", sockfd);
        connections_.erase(sockfd);
    }

    // 检查sockfd的connection是否存在
    bool IsConnectionSafe(int sockfd)
    {
        auto iter = connections_.find(sockfd);
        return iter != connections_.end();
    }

    // 分配器
    void Dispatcher(int timeout = 3000)
    {
        // epoll等待事件就绪，如果有事件就绪，就返回n个就绪事件
        int n = epoll_ptr->EpollerWait(recvs, num, timeout);
        for (int i = 0; i < n; i++)
        {
            // 获取事件
            uint32_t event = recvs[i].events;
            // 获取fd
            int sockfd = recvs[i].data.fd;

            // 如果文件描述符为错误或者挂断，就将其添加进读写事件，下一次会直接进入读或者写处理异常。
            if (event & EPOLLERR)
            {
                event |= EPOLLIN | EPOLLOUT;
            }
            if (event & EPOLLHUP)
            {
                event |= EPOLLIN | EPOLLOUT;
            }

            // 读事件处理，判断fd对应的connection是否存在以及它的回调方法是否以及填写，不然会发出错误。
            if ((event & EPOLLIN) && IsConnectionSafe(sockfd))
            {
                if (connections_[sockfd]->recvcb_)
                    connections_[sockfd]->recvcb_(connections_[sockfd]);
            }

            if ((event & EPOLLOUT) && IsConnectionSafe(sockfd))
            {
                if (connections_[sockfd]->sendcb_)
                    connections_[sockfd]->sendcb_(connections_[sockfd]);
            }
        }
    }

    // 打印我们的connection
    void PrintConnections()
    {
        std::cout << "connections list: ";
        for (auto &connection : connections_)
        {
            std::cout << connection.second->GetSockFd() << ", ";
            std::cout << "inbuffer: " << connection.second->GetInbuffer().c_str();
        }
        std::cout << std::endl;
    }

private:
    // listen套接字
    std::shared_ptr<Sock> listensock_;
    // epoll对象，管理我们的事件
    std::shared_ptr<Epoller> epoll_ptr;
    // 存储fd对应的connection对象，方便对每个fd缓冲区操作
    std::unordered_map<int, std::shared_ptr<Connection>> connections_;

    // 用来存储就绪队列的fd
    struct epoll_event recvs[num];

    // 用来bind
    uint16_t port_;

    func_t onhandler_;
};
